//
//  TFDownloadFileOperation.m
//  离线断点下载_NSURLSeesion
//
//  Created by Guo Rui on 2017/9/7.
//  Copyright © 2017年 GR Harry. All rights reserved.
//

#import "TFDownloadFileOperation.h"

/** 下载文件的下载信息文件缓存路径
 *  文件结构:
 *  @{ @"http:xxxx.mp4" : @{ @"localFileName" : @"xxx.mp4", @"totalSize" : @"xxxxxx" } }
 */
#define CACHEPATH_FILEINFO [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"downloadFileInfo.plist"]

/**
 *  下载文件在沙盒中的缓存路径
 */
#define DOWNLOADFILE_PATH(a) [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:a]

/**
 *  下载信息中的键值
 */
static NSString * const kLocalFileName   = @"localFileName";
static NSString * const kTotalSize       = @"totalSize";

@interface TFDownloadFileOperation ()<NSURLSessionDataDelegate>

@property (strong, nonatomic) NSURLSessionDataTask *downloadTask;
@property (strong, nonatomic) NSURLSession *session;
@property (strong, nonatomic) NSFileHandle *fileHandle;

@property (assign, nonatomic) NSInteger totalSize;
@property (assign, nonatomic) NSInteger reciveSize;

@property (copy, nonatomic) NSString *urlString;
@property (copy, nonatomic) NSString *downloadFilePath;

@property (strong, nonatomic) dispatch_queue_t ioQueue;
@end

@implementation TFDownloadFileOperation


- (instancetype)initWithDownloadUrl:(NSString *)url
{
    if (self = [super init]) {
        self.urlString = url;
    }
    return self;
}

- (void)main
{
    @try {
        @autoreleasepool {
            if ([self isCancelled]) {
                return;
            }
            
            // 1. 检查下载是否完成。下载的文件存在 && 文件信息中没有该url的记录
            if ([[NSFileManager defaultManager] fileExistsAtPath:self.downloadFilePath] && NO == [self fileInfoExist]) {
                // 直接结束
                NSLog(@"直接完成");
                [self willChangeValueForKey:@"isFinished"];
                finished = YES;
                [self didChangeValueForKey:@"isFinished"];
                return;
            }
            
            // 2. 初始化参数
            [self initial];
            
            // 3. 开始下载
            [self.downloadTask resume];
        }
    } @catch (NSException *exception) {
        NSLog(@"exception - %@", exception);
    }
}

#pragma mark - Public
- (void)cancel
{
    [super cancel];
    
    [self.downloadTask cancel];
    self.downloadTask = nil;
    
    [self cacheDownloadInfo];
    
    [self willChangeValueForKey:@"isFinished"];
    finished = YES;
    [self didChangeValueForKey:@"isFinished"];
}

- (void)pause
{
    [self.downloadTask suspend];
    
    [self cacheDownloadInfo];
}

- (void)resume
{
    [self.downloadTask resume];
    
    [self cacheDownloadInfo];
}

#pragma mark - Private
- (void)initial
{
    // 1. 检查缓存
    __block NSMutableDictionary *downloadInfoDict = nil;
    dispatch_barrier_sync(self.ioQueue, ^{
        NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:CACHEPATH_FILEINFO];
        if (nil == dict) {
            dict = [NSMutableDictionary dictionaryWithCapacity:0];
        }
        
        downloadInfoDict = [[dict objectForKey:self.urlString] mutableCopy];
        if (nil == downloadInfoDict) {
            downloadInfoDict = [NSMutableDictionary dictionaryWithCapacity:0];
            
            [downloadInfoDict setObject:[[NSURL URLWithString:self.urlString] lastPathComponent] forKey:kLocalFileName];
        
            [dict setObject:downloadInfoDict forKey:self.urlString];
            
            // 写入沙盒
            [dict writeToFile:CACHEPATH_FILEINFO atomically:YES];
        }
    });
    
    // 2. 初始化状态量
    self.totalSize = [[downloadInfoDict objectForKey:kTotalSize] integerValue];
    
    if ([[NSFileManager defaultManager] fileExistsAtPath:self.downloadFilePath]) {
        NSDictionary *fileInfoDict = [[NSFileManager defaultManager] attributesOfItemAtPath:self.downloadFilePath error:nil];
        self.reciveSize = [fileInfoDict[@"NSFileSize"] integerValue];
    }
}

/**
 *  缓存下载信息
 */
- (void)cacheDownloadInfo
{
    dispatch_barrier_sync(self.ioQueue, ^{
        NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:CACHEPATH_FILEINFO];
        NSMutableDictionary *downloadInfoDict = [[dict objectForKey:self.urlString] mutableCopy];
        [downloadInfoDict setObject:@(self.totalSize) forKey:kTotalSize];
        
        [dict setObject:downloadInfoDict forKey:self.urlString];
        
        // 写入沙盒
        [dict writeToFile:CACHEPATH_FILEINFO atomically:YES];
    });
}

- (void)removeCache
{
    dispatch_barrier_sync(self.ioQueue, ^{
        NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:CACHEPATH_FILEINFO];
        [dict removeObjectForKey:self.urlString];
        [dict writeToFile:CACHEPATH_FILEINFO atomically:YES];
    });
}

/**
 *  @brief 判断当前url在缓存中是否有记录
 *
 *  @return YES / NO
 */
- (BOOL)fileInfoExist
{
    __block NSMutableDictionary *downloadInfoDict = nil;
    
    dispatch_barrier_sync(self.ioQueue, ^{
        NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:CACHEPATH_FILEINFO];
        downloadInfoDict = [dict objectForKey:self.urlString];
    });
    
    return downloadInfoDict != nil;
}

#pragma mark - Getters
- (NSURLSession *)session
{
    if (!_session) {
        _session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[[NSOperationQueue alloc] init] ];
    }
    return _session;
}

- (NSURLSessionDataTask *)downloadTask
{
    if (!_downloadTask) {
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.urlString]];
        NSString *range = [NSString stringWithFormat:@"bytes=%zd-", self.reciveSize];
        [request setValue:range forHTTPHeaderField:@"Range"];
        
        _downloadTask = [self.session dataTaskWithRequest:request];
    }
    return _downloadTask;
}

- (NSString *)downloadFilePath
{
    if (!_downloadFilePath) {
        _downloadFilePath = DOWNLOADFILE_PATH([[NSURL URLWithString:self.urlString] lastPathComponent]);
    }
    return _downloadFilePath;
}

- (NSFileHandle *)fileHandle
{
    if (!_fileHandle) {
        
        // 用文件句柄打开文件前，需要先创建文件
        if (NO == [[NSFileManager defaultManager] fileExistsAtPath:self.downloadFilePath]) {
            [[NSFileManager defaultManager] createFileAtPath:self.downloadFilePath contents:nil attributes:nil];
        }
        
        _fileHandle = [NSFileHandle fileHandleForWritingAtPath:self.downloadFilePath];
        [_fileHandle seekToEndOfFile];
    }
    return _fileHandle;
}

- (dispatch_queue_t)ioQueue
{
    if (!_ioQueue) {
        _ioQueue = dispatch_queue_create("cn.timeface.downloadfile", DISPATCH_QUEUE_SERIAL);
    }
    return _ioQueue;
}
#pragma mark - NSURLSessionDataTaskDelegate
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
{
    if (0 == self.totalSize) {
        self.totalSize = response.expectedContentLength;
        // 记录当前的下载状态
        [self cacheDownloadInfo];
    }
    
    completionHandler(NSURLSessionResponseAllow);
}

- (void)URLSession:(NSURLSession *)session dataTask:(nonnull NSURLSessionDataTask *)dataTask didReceiveData:(nonnull NSData *)data
{
    self.reciveSize += data.length;
    [self.fileHandle writeData:data];
    
    NSLog(@"%lf", 1.0 * self.reciveSize / self.totalSize);
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
    
    // 1. 清除状态
    [self.fileHandle closeFile];
    self.fileHandle = nil;
    self.downloadTask = nil;
    
    // 2. 修改缓存
    if (error) {
        // 下载失败
        [self cacheDownloadInfo];
    }
    else {
        // 下载成功，将下载信息从缓存中移除
        [self removeCache];
    }
    
    // 3. 更改operation状态
    [self willChangeValueForKey:@"isFinished"];
    finished = YES;
    [self didChangeValueForKey:@"isFinished"];
    NSLog(@"%@", self.downloadFilePath);
}

- (void)dealloc {
    [self.session finishTasksAndInvalidate];
    NSLog(@"\n************\n %@ dealloc \n************", self.class);
}

#pragma mark - Comman
- (void)start
{
    if ([self isCancelled]) {
        [self willChangeValueForKey:@"isFinished"];
        finished = YES;
        [self didChangeValueForKey:@"isFinished"];
        return;
    }
    
    [self willChangeValueForKey:@"isExecuting"];
    [self main];
    executing = YES;
    [self didChangeValueForKey:@"isExecuting"];
}

- (BOOL)isAsynchronous
{
    return YES;
}

- (BOOL)isExecuting
{
    return executing;
}

- (BOOL)isFinished
{
    return finished;
}

@end
